#include <crtdbg.h>
#include <direct.h>
#include <stdio.h>

#include "toolbox_interchange.hpp"

#include "rphoton.hpp"
#include "../q3map/radiosity.h"
#include "rdxsupport.hpp"
#include "../tdp2/tdp2_visualresources.hpp"
#include "../tdp2/tdp2_byteorder.hpp"
#include "../tdp2/tdp2_threading.hpp"
#include "../rdistrolib/distributionsession.h"
#include "../rdistrolib/jobtypes.h"

using namespace RDX;
using namespace RDX::ObjectManagement;
using namespace TDP;
using namespace TDP::Math;
using namespace TDP::Resources;
using namespace TDP::ByteOrder;
using namespace RDistro;

// Scanline lightmap caster
// - Manifolds are constructed from map geometry.  A manifold is one batchgroup segment.
// - Each segment belongs to one chart
// - Multiple segments may be on the same chart, i.e. terrain
// - Primary lights are per manifold, not per chart.
//
// RRAD generates collection bounces.
// Collection points that are inside solid volumes or too close to other map geometry don't receive bounces

extern TDP::Threading::MultilevelThreadDispatcher *mlDispatcher;

FVec3 ParseVector(const CString &str)
{
	int firstSpace = -1;
	int secondSpace = -1;

	for(int i=0;i<str.Length();i++)
	{
		if(str[i] == ' ')
		{
			if(firstSpace == -1)
				firstSpace = i;
			else
				secondSpace = i;
		}
	}

	if(secondSpace == -1)
		return FVec3(0.0f, 0.0f, 0.0f);

	{
		CString subs[3];
		subs[0].SetSubString(NULL, str, 0, firstSpace);
		subs[1].SetSubString(NULL, str, firstSpace+1, secondSpace-firstSpace-1);
		subs[2].SetSubString(NULL, str, secondSpace+1, str.Length()-secondSpace-1);

		return FVec3(
			subs[0].ToFloat(), subs[1].ToFloat(), subs[2].ToFloat()
			);
	}
}




rpConfig config;
RDX::ObjectManagement::CRef<TDP::Resources::WorldModel> geometry;
LinkedList<rpLightVolume> lights(TDP::Memory::DefaultAllocator());



void LoadObjects(const CString &path, LinkedList<rpObject> *objects)
{
	rpObject *currentObject = NULL;

	FILE *stream = fopen(path.Chars(), "rb");
	unsigned int l;

	while(fread(&l, 4, 1, stream))
	{
		if(l == 0)
			currentObject = NULL;
		else
		{
			Vector<char> key(TDP::Memory::DefaultAllocator());
			key.Resize(NULL, l);
			fread(key, l, 1, stream);

			fread(&l, 4, 1, stream);
			Vector<char> value(TDP::Memory::DefaultAllocator());
			value.Resize(NULL, l);
			fread(value, l, 1, stream);

			if(!currentObject)
				currentObject = objects->CreateLinkAtEnd()->DataPtr();

			rpObjectProperty *op = currentObject->properties.CreateLinkAtEnd()->DataPtr();
			op->key.Set(NULL, key, key.Count());
			op->value.Set(NULL, value, value.Count());
		}
	}

	fclose(stream);
}

Vector<rpManifold> manifolds(TDP::Memory::DefaultAllocator());
Vector<rpManifoldList> faceManifolds(TDP::Memory::DefaultAllocator());
Vector<rpManifoldList> chartManifolds(TDP::Memory::DefaultAllocator());

void CreateLights(const LinkedList<rpObject> *objects, LinkedList<rpLight> *lights)
{
	// Create point light shooters
	Int32 numPLights = 0;
	for(const ListLink<rpObject> *objLL = objects->First();objLL;objLL=objLL->Next())
	{
		const rpObject *obj = objLL->DataPtr();

		const rpObjectProperty *classname = obj->GetProperty("classname");
		const rpObjectProperty *intensity = obj->GetProperty("light");
		if(!intensity)
			intensity = obj->GetProperty("_light");
		const rpObjectProperty *color = obj->GetProperty("_color");
		const rpObjectProperty *origin = obj->GetProperty("origin");
		const rpObjectProperty *spawnflags = obj->GetProperty("spawnflags");

		if(!classname || !origin )
			continue;

		if(classname->value == "light")
		{
			float intensityV = config.defaultPointIntensity;
			if(intensity)
				intensityV = intensity->value.ToFloat();

			FVec3 colorV = FVec3(1.0f);
			if(color)
				colorV = ParseVector(color->value);

			float softV = config.defaultLightEmissionRadius;

			rpLight *light = lights->CreateLinkAtEnd()->DataPtr();

			light->lightType = rpLight::rpLightTypePoint_k;
			light->virtualEmissionRadius = softV;
			light->isSpotlight = false;
			light->origin = ParseVector(origin->value);
			light->emission = colorV * intensityV * config.photonsPointScale;
			light->primaryFragmentation = 0.0f;
			light->isPrimary = false;
			light->primaryNoVisibilityFalloff = false;
			light->lightIndex = -1;

			if(spawnflags)
			{
				UInt32 flags = static_cast<UInt32>(spawnflags->value.ToInt());
				if(flags & (1 << 5))	// PRIMARY
				{
					light->isPrimary = true;
					light->lightIndex = numPLights++;
					light->primaryFragmentation = 1.0f;
					if(flags & (1 << 6))	// ROTATABLE
						light->primaryNoVisibilityFalloff = true;
				}
			}
		}
	}
}

void ExportPrimaryLightInfo(const CString &pliPath, const LinkedList<rpLight> *lights)
{
	UInt32 numPL = 0;
	for(const ListLink<rpLight> *lightLL = lights->First();lightLL;lightLL=lightLL->Next())
	{
		if(lightLL->Data().isPrimary)
			numPL++;
	}

	FILE *plStream = fopen(pliPath.Chars(), "wb");
	if(!plStream)
	{
		printf("WARNING: There was a problem opening %s\n", pliPath.Chars());
		return;
	}

	fwrite(&numPL, sizeof(numPL), 1, plStream);

	// Export lights
	for(const ListLink<rpLight> *lightLL = lights->First();lightLL;lightLL=lightLL->Next())
	{
		if(lightLL->Data().isPrimary)
		{
			const rpLight *light = lightLL->DataPtr();
			TDP::Resources::WorldModel::PrimaryLight pl;
			pl.origin = light->origin;
			pl.direction = light->direction;
			pl.emission = light->emission;
			pl.isSky = (light->lightType == light->rpLightTypeSky_k);
			fwrite(&pl, sizeof(pl), 1, plStream);
		}
	}

	// Export manifold-to-light mappings
	{
		UInt32 manifoldCount = static_cast<UInt32>(manifolds.Count());
		fwrite(&manifoldCount, sizeof(manifoldCount), 1, plStream);
	}
	for(LargeUInt i=0;i<manifolds.Count();i++)
	{
		const rpManifold *manifold = manifolds + i;
		fwrite(&manifold->modelIndex, sizeof(UInt32), 1, plStream);
		fwrite(&manifold->batchGroupIndex, sizeof(UInt32), 1, plStream);
		fwrite(&manifold->segmentIndex, sizeof(UInt32), 1, plStream);
		fwrite(&manifold->primaryLightIndex, sizeof(Int32), 1, plStream);
	}

	fclose(plStream);
}


void rpManifold::CalculateCullInfo()
{
	FVec3 mins, maxs;

	unsigned int numIndexes = this->seg->numTris*3;
	const rpPackedVertex *vertData = this->vertData;
	const UShort *idxData = this->triData;

	// Find the approximate center
	for(unsigned int idx=0;idx<numIndexes;idx++)
	{
		FVec3 pos = vertData[triData[idx]].position;

		if(idx == 0)
		{
			mins = pos;
			maxs = pos;
		}
		else
		{
			mins = mins.Minimum(pos);
			maxs = maxs.Maximum(pos);
		}
	}

	FVec3 center = (mins + maxs) * 0.5f;
	this->cullSphereOrigin = center;

	float radiusSquared = 0.0f;
	for(unsigned int idx=0;idx<numIndexes;idx++)
	{
		FVec3 pos = vertData[triData[idx]].position;
		float distSquared = (pos - center).LengthSquared();

		if(distSquared > radiusSquared)
			radiusSquared = distSquared;
	}

	this->cullSphereRadius = sqrtf(radiusSquared);
}

static void ParseHex(UInt8 *key, const char *chars)
{
	bool high = true;
	while(*chars)
	{
		char c = *chars;
		int v = 0;
		if(c >= '0' && c <= '9')
			v = c - '0';
		else if(c >= 'a' && c <= 'f')
			v = c - 'a' + 0xa;
		else if(c >= 'A' && c <= 'F')
			v = c - 'A' + 0xa;

		if(high)
		{
			key[0] = static_cast<UInt8>(v << 4);
			high = false;
		}
		else
		{
			key[0] |= v;
			key++;
			high = true;
		}
		chars++;
	}
}

int RPhoton_Main()
{
	LinkedList<rpObject> objects(TDP::Memory::DefaultAllocator());

	config.photonsPoint = 600000;
	config.photonsPer100squ = 10;
	config.photonsPointScale = (TDP::Math::Pi_k * 4.0f) * 7500.0f / 256.0f;
	config.collectionPointRadius = 4.0f;
	config.defaultPointIntensity = 300.0f;
	config.traceDistance = 30000.0f;
	config.maxDeflections = 5;
	config.alwaysDeflect = false;
	config.maxPhotonsPerManifold = 10000000;
	config.inputSubchartSize = 512;
	config.maxUnbiasAmplification = 256.0f;
	config.chartMode = RP_CHARTMODE_SUPERDELUXE;
	//config.collectionMode = RP_COLLECTIONMODE_FIXED_RADIUS;
	config.collectionMode = RP_COLLECTIONMODE_FIXED_PHOTON_COUNT;
	config.disableTreeCull = false;
	config.defaultLightEmissionRadius = 0.0f;

	config.numFinalGatherShots = 100;
	config.chartSupersampling = 8;
	config.radiosityExclusionRadius = 2.0f;
	config.radiosityPushoffDistance = 3.0f;	// Needs to be greater than the exclusion radius

	config.radiosityExportPoints = false;
	config.radiosityAccumulate = false;
	config.castLights = false;
	config.noDirect = false;
	config.radiosityGranularity = 2;

	config.distributionServer = NULL;
	config.distributionKey = NULL;
	config.distributionSlave = false;
	config.distributionSession = NULL;

	for(int i=2;i<toolResources.argc;i++)
	{
		const char *arg = toolResources.argv[i];
		int availableArgs = toolResources.argc - i - 1;
		if(!strcmp(arg, "-ss") && availableArgs >= 2)
		{
			i++;
			config.chartSupersampling = atoi(toolResources.argv[i]);
		}
		else if(!strcmp(arg, "-distributemaster") && availableArgs >= 3)
		{
			i++;
			config.distributionServer = toolResources.argv[i];
			i++;
			config.distributionKey = toolResources.argv[i];
			config.distribute = true;
			config.distributionSlave = false;
		}
		else if(!strcmp(arg, "-distributeslave") && availableArgs >= 4)
		{
			i++;
			config.distributionServer = toolResources.argv[i];
			i++;
			config.distributionKey = toolResources.argv[i];
			i++;
			ParseHex(config.distributionJobKey.data, toolResources.argv[i]);

			config.distribute = true;
			config.distributionSlave = true;
		}
		else if(!strcmp(arg, "-radexport"))
			config.radiosityExportPoints = true;
		else if(!strcmp(arg, "-radaccumulate"))
			config.radiosityAccumulate = true;
		else if(!strcmp(arg, "-castdirect"))
			config.castLights = true;
		else if(!strcmp(arg, "-nodirect"))
			config.noDirect = true;
		else if(!strcmp(arg, "-granularity") && availableArgs >= 2)
		{
			i++;
			config.radiosityGranularity = atoi(toolResources.argv[i]);
		}
	}


	if(config.alwaysDeflect)
		config.collectionPointPhotons = config.photonsPoint * (config.maxDeflections + 1) / 4800;
	else
		config.collectionPointPhotons = config.photonsPoint * 8 / 4800;
	config.photonsPerCatalogCluster = config.collectionPointPhotons;

	if(toolResources.argc < 2)
	{
		fprintf(stderr, "Syntax: rphoton <input>");
		return -1;
	}

	const char *mapName = toolResources.argv[toolResources.argc-1];
	mlDispatcher = new TDP::Threading::MultilevelThreadDispatcher(TDP::Threading::CountProcessors() - 1);

	// Synchronize files if we're distributed
	if(config.distribute)
	{
		char fname[4096];

		printf("Synchronizing files with distribution server...\n");
		DistributionSession *session = CreateDistributionSession_StringKey(config.distributionServer, config.distributionKey);
		while(!session->LoggedIn() && session->ConnectionActive())
		{
			Sleep(10);
			session->ProcessData();
		}

		sprintf(fname, "rdxclasses\\TDP\\Resources\\Maps\\%s.rxt", mapName);
		session->SynchronizeFile(fname, !config.distributionSlave, false);
		session->SynchronizeFile("rdxclasses\\TDPTools.zonepolicy", !config.distributionSlave, false);
		session->SynchronizeFile("rdxclasses\\Core.rxt", !config.distributionSlave, false);
		session->SynchronizeFile("rdxclasses\\TDP\\Math.rxt", !config.distributionSlave, false);
		session->SynchronizeFile("rdxclasses\\TDP\\Resources.rxt", !config.distributionSlave, false);

		sprintf(fname, "generated\\maps\\%s.resbundle", mapName);
		session->SynchronizeFile(fname, !config.distributionSlave, config.distributionSlave);
		sprintf(fname, "maps\\%s.objects", mapName);
		session->SynchronizeFile(fname, !config.distributionSlave, config.distributionSlave);
		DestroyDistributionSession(session);
	}

	RDX::OperationContext ctx(toolResources.objm);

	CRef<const String> str;
	{
		CString wmStr(TDP::Memory::DefaultAllocator());
		wmStr.Set(NULL, "TDP.Resources.Maps.");
		wmStr.Append(NULL, mapName);
		wmStr.Append(NULL, ".Data.worldModel");
		str = toolResources.objm->CreateStringASCII(&ctx, wmStr.Chars());
	}

	CRef<void> mapObject = toolResources.objm->LookupSymbol(&ctx, str, toolResources.pkgHost);
	//toolResources.objm->SavePackage(&ctx, GCInfo::From(mapObject)->domain, toolResources.pkgHost);

	{
		// Open geo file
		geometry = mapObject.Cast<TDP::Resources::WorldModel>();
		const Char *mapObjectType = GCInfo::From(geometry->geometryData)->gstSymbol->AsChars();
		const StructuredType *st = static_cast<const StructuredType *>(GCInfo::From(geometry->geometryData)->containerType);
		int n = 0;
	}

	{
		printf("Loading objects...\n");
		CString objectsPath(NULL, TDP::Memory::DefaultAllocator(), "maps/");
		objectsPath.Append(NULL, mapName);
		objectsPath.Append(NULL, ".objects");

		LoadObjects(objectsPath, &objects);
	}

	{
		CString str(NULL, TDP::Memory::DefaultAllocator(), "maps/");
		str.Append(NULL, mapName);
		str.Append(NULL, "_photons");
		_mkdir( str.Chars() );
	}

	// Create valid manifolds
	unsigned int numManifolds = 0;
	LargeInt nModels = GCInfo::From(geometry->models)->numElements;
	for(LargeInt modelIdx=0;modelIdx<nModels;modelIdx++)
	{
		const rpModel *model = geometry->models + modelIdx;
		LargeInt nBatchGroups = GCInfo::From(model->clusterInfos)->numElements;
		for(LargeInt clusterIdx=0;clusterIdx<nBatchGroups;clusterIdx++)
		{
			const rpBatchGroup *bg = model->clusterInfos + clusterIdx;
			LargeInt nSegments = GCInfo::From(bg->segmentInfos)->numElements;
			for(unsigned int segIdx=0;segIdx<nSegments;segIdx++)
			{
				const rpSegment *seg = bg->segmentInfos + segIdx;

				// Only take objects at maximum LOD
				if(seg->lodControl == false || seg->lodMin == 0.0f)
					numManifolds++;
			}
		}
	}

	manifolds.Resize(NULL, numManifolds);
	faceManifolds.Resize(NULL, geometry->numFaces);
	chartManifolds.Resize(NULL, geometry->numCharts);

	numManifolds = 0;

	{
		FILE *bundleF = _wfopen(geometry->geometryData->owner->binaryFileName->AsChars(), L"rb");
		fseek(bundleF, geometry->geometryData->dataOffset, SEEK_SET);

		for(LargeInt i=0;i<nModels;i++)
		{
			const rpModel *model = geometry->models + i;
			LargeInt nBatchGroups = GCInfo::From(model->clusterInfos)->numElements;
			for(unsigned int j=0;j<nBatchGroups;j++)
			{
				const rpBatchGroup *bg = model->clusterInfos + j;
				const rpGCluster *cluster = geometry->geometryData->clusters + model->firstCluster + j;
				unsigned int vertOffset = 0;
				unsigned int triOffset = 0;

				LargeInt nSegments = GCInfo::From(bg->segmentInfos)->numElements;

				LargeInt totalVerts = 0;
				for(LargeInt k=0;k<nSegments;k++)
					totalVerts += cluster->segments[k].numVerts;

				CRef<PackedWorldVertex> loadedVerts = toolResources.objm->Create1DArray<PackedWorldVertex>(&ctx, totalVerts);
				fread(loadedVerts, sizeof(PackedWorldVertex) * totalVerts, 1, bundleF);

				// Determine of these need to be swapped
				// This must matched PackedWorldVertex
				
				for(LargeInt vi=0;vi<totalVerts;vi++)
				{
					PackedWorldVertex &vert = loadedVerts[vi];
					for(int j=0;j<2;j++)
						vert.lmTexCoords[j] = ByteOrder::Little(vert.lmTexCoords[j]);
					for(int j=0;j<3;j++)
						vert.position[j] = ByteOrder::Little(vert.position[j]);
					for(int j=0;j<3;j++)
						vert.svec[j] = ByteOrder::Little(vert.svec[j]);
					for(int j=0;j<2;j++)
						vert.texCoords[j] = ByteOrder::Little(vert.texCoords[j]);
					for(int j=0;j<4;j++)
						vert.tvecN[j] = ByteOrder::Little(vert.tvecN[j]);
				}


				for(unsigned int k=0;k<nSegments;k++)
				{
					const rpSegment *seg = bg->segmentInfos + k;
					const rpGSegment *bundleSeg = cluster->segments + k;

					CRef<UInt16> loadedTris = toolResources.objm->Create1DArray<UInt16>(&ctx, bundleSeg->numTris * 3);
					fread(loadedTris, sizeof(UInt16) * bundleSeg->numTris * 3, 1, bundleF);

					// Only take objects at maximum LOD
					if(seg->lodControl == false || seg->lodMin == 0.0f)
					{
						manifolds[numManifolds].bg = bg;
						manifolds[numManifolds].seg = seg;
						manifolds[numManifolds].vertData = loadedVerts;
						manifolds[numManifolds].triData = loadedTris;
						manifolds[numManifolds].modelIndex = static_cast<UInt32>(i);
						manifolds[numManifolds].batchGroupIndex = static_cast<UInt32>(j);
						manifolds[numManifolds].segmentIndex = static_cast<UInt32>(k);

						manifolds[numManifolds].CalculateCullInfo();
					
						// Store a ref to this
						*(chartManifolds[seg->originalChartIndex].manifoldList.CreateLinkAtEnd()->DataPtr()) = manifolds + numManifolds;
						*(faceManifolds[seg->originalFaceIndex].manifoldList.CreateLinkAtEnd()->DataPtr()) = manifolds + numManifolds;
						numManifolds++;
					}

					vertOffset += seg->numVerts;
					triOffset += seg->numTris;
				}
			}
		}
		fclose(bundleF);
	}

	LinkedList<rpLight> lights(TDP::Memory::DefaultAllocator());

	// Create lights
	CreateLights(&objects, &lights);

	printf("TASK: Forming charts...\n");
	{
		DistributionSession *session = NULL;
		bool runChartJobs = true;

		if(config.distribute)
		{
			session = CreateDistributionSession_StringKey(config.distributionServer, config.distributionKey);
			while(!session->LoggedIn() && session->ConnectionActive())
			{
				Sleep(10);
				session->ProcessData();
			}

			// Install the job
			if(!config.distributionSlave)
			{
				config.distributionJobKey = session->CreateRandomJobKey(JobTypes::Lighting_Direct);
				session->CreateJob(config.distributionJobKey, geometry->numCharts, 1, mapName);
				Sleep(100);
			}

			if(!session->LoggedIn())
			{
				fprintf(stderr, "Couldn't establish a connection to the distribution server\n");
				if(config.distributionSlave)
				{
					DestroyDistributionSession(session);
					session = NULL;
					runChartJobs = false;
				}
				else
				{
					DestroyDistributionSession(session);
					session = NULL;
					fprintf(stderr, "... Working offline instead\n");
					config.distribute = false;
				}
			}

			config.distributionSession = session;
		}

		if(runChartJobs)
		{
			CString mapPath(NULL, TDP::Memory::DefaultAllocator(), "maps/");
			mapPath.Append(NULL, mapName);
			CreateCharts(mapPath, &lights);
		}

		if(config.distribute)
		{
			if(session)
			{
				if(!config.distributionSlave)
					session->DestroyJob(config.distributionJobKey);
				DestroyDistributionSession(session);
			}
			session = NULL;
		}
	}

	// TODO: Rechart

	// Create the radiosity project
	if(!config.distributionSlave)
	{
		CString radProjectPath(NULL, TDP::Memory::DefaultAllocator(), "maps/");
		radProjectPath.Append(NULL, mapName);
		radProjectPath.Append(NULL, ".radproject");

		FILE *chartProjStream = fopen( radProjectPath.Chars(), "wb");
		rradProjectSettings_t projSettings;
		projSettings.numCharts = geometry->numCharts;

		fwrite(&projSettings, sizeof(projSettings), 1, chartProjStream);
		fclose(chartProjStream);
	}

	// Find how many lightmaps to generate
	printf("TASK: Exporting lightmaps...\n");
	if(!config.distributionSlave)
	{
		CString mapPath(NULL, TDP::Memory::DefaultAllocator(), "maps/");
		mapPath.Append(NULL, mapName);
		CreateLightmaps(mapPath);
	}
	
	printf("TASK: Exporting primary light info...\n");
	if(!config.distributionSlave)
	{
		CString mapPath(NULL, TDP::Memory::DefaultAllocator(), "maps/");
		mapPath.Append(NULL, mapName);
		mapPath.Append(NULL, "_photons/primarylights");
		ExportPrimaryLightInfo(mapPath, &lights);
	}

	// Release resources
	printf("TASK: Releasing resources...\n");
	geometry = NULL;
	manifolds.Release();
	faceManifolds.Release();
	chartManifolds.Release();
	lights.DeleteAllLinks();
	delete mlDispatcher;

	printf("FINISHED\n");


	return 0;
}
